package com.beta.service.impl;

import com.baomidou.framework.service.impl.SuperServiceImpl;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.beta.constant.CommonConstant;
import com.beta.em.order.OrderItemStatus;
import com.beta.em.order.PaymentStatus;
import com.beta.em.order.RefundStatus;
import com.beta.em.order.TransactionType;
import com.beta.entity.*;
import com.beta.mapper.CleanTxnDetailsMapper;
import com.beta.service.*;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * CleanTxnDetails 表数据服务层接口实现类
 */
@Service
public class CleanTxnDetailsService extends SuperServiceImpl<CleanTxnDetailsMapper, CleanTxnDetails> implements ICleanTxnDetailsService {

    @Autowired
    private IDateTimeService dateTimeService;
    @Autowired
    private IOrdersService ordersService;
    @Autowired
    private IOrderStatusService orderStatusService;
    @Autowired
    private IOrderItemsService orderItemsService;
    @Autowired
    private IProductItemsService productItemsService;
    @Autowired
    private ProductsService productsService;
    @Autowired
    private IUsersService usersService;
    @Autowired
    private IStockLocationsService stockLocationsService;
    @Autowired
    private IPaymentsService paymentsService;
    @Autowired
    private IShipmentsService shipmentsService;
    @Autowired
    private IOltpTxnLogsService oltpTxnLogsService;

    @Override
    public void recordOrderPayment(Orders orders) {
        Payments payment = paymentsService.getPaymentByOrderId(orders.getId());
        if (Objects.isNull(payment) || !PaymentStatus.PAID.getValue().equals(payment.getStatus())) {
            return;
        }
        doRecordOrderTxnDetail(orders, orderItem -> new CleanTxnDetails()
                .setBuyAt(orders.getCreatedAt())
                .setPayAt(payment.getCreatedAt()));
    }

    @Override
    public void recordOrderAssigning(Orders orders) {
        doRecordOrderTxnDetail(orders, orderItems -> new CleanTxnDetails()
                .setAssignCourierAt(orders.getUpdatedAt()));
    }

    @Override
    public void recordOrderPicking(Orders orders) {
        Shipments shipment = shipmentsService.getShipmentByOrderId(orders.getId());
        if (Objects.isNull(shipment)) {
            return;
        }
        doRecordOrderTxnDetail(orders, orderItems -> new CleanTxnDetails()
                .setCourierConfirmAt(shipment.getPickedAt()));
    }

    @Override
    public void recordOrderShipping(Orders orders) {
        Shipments shipment = shipmentsService.getShipmentByOrderId(orders.getId());
        if (Objects.isNull(shipment)) {
            return;
        }
        doRecordOrderTxnDetail(orders, orderItems -> new CleanTxnDetails()
                .setShipAt(shipment.getShippedAt()));
    }

    @Override
    public void recordOrderReceived(Orders orders) {
        Shipments shipment = shipmentsService.getShipmentByOrderId(orders.getId());
        if (Objects.isNull(shipment)) {
            return;
        }
        doRecordOrderTxnDetail(orders, orderItems -> new CleanTxnDetails()
                .setReceiveAt(shipment.getCompletedAt()));
    }

    @Override
    public void recordOrderItemRefund(OrderItems orderItems) {
        doRecordOrderItemTxnDetail(orderItems, () -> {
            if (OrderItemStatus.REJECTED.getValue().equals(orderItems.getStatus())) {
                return new CleanTxnDetails()
                        .setRejectAt(orderItems.getUpdatedAt())
                        .setRefundRate(orderItems.getRefundRate());
            }
            return null;
        });
    }

    @Override
    public void recordOrderItemRefundAgree(OrderItems orderItems) {
        doRecordOrderItemTxnDetail(orderItems, () -> {
            if (OrderItemStatus.REJECTED.getValue().equals(orderItems.getStatus())
                    && RefundStatus.AGREED.getValue().equals(orderItems.getRefundStatus())) {
                return new CleanTxnDetails()
                        .setRejectAgreeAt(orderItems.getUpdatedAt());
            }
            return null;
        });
    }

    @Override
    public void recordOrderItemRefundSettled(OrderItems orderItems) {
        doRecordOrderItemTxnDetail(orderItems, () -> {
            if (OrderItemStatus.REJECTED.getValue().equals(orderItems.getStatus())
                    && RefundStatus.AGREED.getValue().equals(orderItems.getRefundStatus())) {
                return new CleanTxnDetails()
                        .setPlatformFee(BigDecimal.ZERO.setScale(2, CommonConstant.ROUNDING_MODE))
                        .setRefundSettledAt(orderItems.getUpdatedAt());
            }
            return null;
        });
    }

    @Override
    public void recordOrderSettled(Orders orders) {
        if (!orderStatusService.isSettled(orders)) {
            return ;
        }

        doRecordOrderTxnDetail(orders, orderItem -> new CleanTxnDetails().setSettledAt(orders.getUpdatedAt()));

        if (hasAdjustRecord(orders.getId())) {
            return;
        }
        createOrderAdjustRecord(orders);
    }

    private boolean hasAdjustRecord(Integer orderId) {
        CleanTxnDetails orderAdjustRecord = baseMapper.getOrderAdjustRecord(orderId);
        return Objects.nonNull(orderAdjustRecord);
    }

    private void createOrderAdjustRecord(Orders orders) {
        List<CleanTxnDetails> orderItemTxns = selectList(new EntityWrapper<>(new CleanTxnDetails().setOrderId(orders.getId())));
        assert CollectionUtils.isNotEmpty(orderItemTxns) : "不应该为空";

        BigDecimal adjustYeepayFee = BigDecimal.ZERO;
        BigDecimal adjustPlatformFee = BigDecimal.ZERO;
        BigDecimal orderPayFee = BigDecimal.ZERO;
        BigDecimal orderPlatformFee = BigDecimal.ZERO;
        List<OltpTxnLogs> orderPayFeeLogs = oltpTxnLogsService.getLogByOrderAndTxnType(orders.getId(), TransactionType.PAY_FEE);
        List<OltpTxnLogs> orderPlatformFeeLogs = oltpTxnLogsService.getLogByOrderAndTxnType(orders.getId(), TransactionType.PLATFORM_FEE);
        for (CleanTxnDetails orderItemTxn : orderItemTxns) {
            adjustYeepayFee = adjustYeepayFee.add(orderItemTxn.getYeepayFee());
            adjustPlatformFee = adjustPlatformFee.add(orderItemTxn.getPlatformFee());
        }
        if (CollectionUtils.isNotEmpty(orderPayFeeLogs)) {
            orderPayFee = orderPayFeeLogs.stream().map(OltpTxnLogs::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        }
        if (CollectionUtils.isNotEmpty(orderPlatformFeeLogs)) {
            orderPlatformFee = orderPlatformFeeLogs.stream().map(OltpTxnLogs::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        }
        adjustYeepayFee = orderPayFee.abs().subtract(adjustYeepayFee);
        adjustPlatformFee = orderPlatformFee.abs().subtract(adjustPlatformFee);
        if (adjustYeepayFee.compareTo(BigDecimal.ZERO) != 0 || adjustPlatformFee.compareTo(BigDecimal.ZERO) != 0) {
            Date dbDate = dateTimeService.toDbDate();
            CleanTxnDetails adjustTxnDetail = new CleanTxnDetails()
                    .setOrderCode(orders.getCode())
                    .setOrderId(orders.getId())
                    .setYeepayFee(adjustYeepayFee)
                    .setPlatformFee(adjustPlatformFee)
                    .setProductName("【冲平】")
                    .setProductDescription("【冲平】")
                    .setUpdatedAt(dbDate)
                    .setCreatedAt(dbDate);
            insertSelective(adjustTxnDetail);
        }
    }

    private void doRecordOrderTxnDetail(Orders orders, Function<OrderItems, CleanTxnDetails> recorder) {
        List<OrderItems> orderItems = orderItemsService.selectList(new EntityWrapper<>(new OrderItems().setOrderId(orders.getId())));
        if (CollectionUtils.isEmpty(orderItems)) {
            return;
        }

        Date dbDate = dateTimeService.toDbDate();
        List<CleanTxnDetails> updateList = new ArrayList<>(orderItems.size());
        for (OrderItems orderItem : orderItems) {
            CleanTxnDetails orderItemTxnDetail = getOrderItemTxnDetail(orderItem);
            CleanTxnDetails updateEntity = recorder.apply(orderItem);
            if (Objects.nonNull(updateEntity)) {
                updateList.add(updateEntity
                        .setId(orderItemTxnDetail.getId())
                        .setUpdatedAt(dbDate));
            }
        }

        if (CollectionUtils.isNotEmpty(updateList)) {
            updateBatchById(updateList);
        }
    }

    private void doRecordOrderItemTxnDetail(OrderItems orderItems, Supplier<CleanTxnDetails> updateEntitySupplier) {
        if (Objects.isNull(orderItems)) {
            return;
        }

        Date dbDate = dateTimeService.toDbDate();
        CleanTxnDetails orderItemTxnDetail = getOrderItemTxnDetail(orderItems);
        CleanTxnDetails updateEntity = updateEntitySupplier.get();
        if (Objects.nonNull(updateEntity)) {
            updateEntity.setId(orderItemTxnDetail.getId()).setUpdatedAt(dbDate);
            updateSelectiveById(updateEntity);
        }
    }

    private CleanTxnDetails getOrderItemTxnDetail(OrderItems orderItem) {
        CleanTxnDetails cleanTxnDetails = selectOne(new CleanTxnDetails().setOrderId(orderItem.getOrderId()).setOrderItemId(orderItem.getId()));
        if (Objects.isNull(cleanTxnDetails)) {
            Orders orders = ordersService.selectById(orderItem.getOrderId());
            return createCleanTxnDetails(orders, orderItem);
        }
        return cleanTxnDetails;
    }

    private CleanTxnDetails createCleanTxnDetails(Orders orders, OrderItems orderItem) {
        ProductItems productItem = productItemsService.selectById(orderItem.getProductItemId());
        Products product = productsService.selectById(productItem.getProductId());
        Users retailer = usersService.selectById(orders.getUserId());
        Users dealer = usersService.selectById(productItem.getUserId());
        StockLocations stockLocation = stockLocationsService.selectById(productItem.getStockLocationId());

        Date dbDate = dateTimeService.toDbDate();
        CleanTxnDetails cleanTxnDetails = new CleanTxnDetails()
                .setCreatedAt(dbDate)
                .setUpdatedAt(dbDate);

        fillOrderInfo(cleanTxnDetails, orders);
        fillOrderItemInfo(cleanTxnDetails, orderItem);
        fillProductItemInfo(cleanTxnDetails, productItem);
        fillProductInfo(cleanTxnDetails, product);
        fillRetailerUserInfo(cleanTxnDetails, retailer);
        fillDealerUserInfo(cleanTxnDetails, dealer, orderItem);
        fillStockLocationInfo(cleanTxnDetails, stockLocation);

        insertSelective(cleanTxnDetails);
        return selectOne(new CleanTxnDetails().setOrderId(orderItem.getOrderId()).setOrderItemId(orderItem.getId()));
    }

    private void fillStockLocationInfo(CleanTxnDetails cleanTxnDetails, StockLocations stockLocation) {
        cleanTxnDetails.setStockLocationId(stockLocation.getId())
                .setStockLocationName(stockLocation.getName())
                .setStockLocationAddress(stockLocation.getAddress());
    }

    private void fillDealerUserInfo(CleanTxnDetails cleanTxnDetails, Users dealer, OrderItems orderItems) {
        if (Objects.isNull(dealer)) {
            return;
        }
        BigDecimal dealerPlatformFeeRatio = usersService.getDealerPlatformFeeRatio(dealer);
        cleanTxnDetails.setDealerUserId(dealer.getId())
                .setDealerUserName(dealer.getUsername())
                .setDealerUserPhone(dealer.getPhone())
                .setDealerUserLedgerCode(dealer.getLedgerCode())
                .setPlatformFee(orderItems.getPrice().multiply(dealerPlatformFeeRatio).setScale(2, CommonConstant.ROUNDING_MODE));
    }

    private void fillRetailerUserInfo(CleanTxnDetails cleanTxnDetails, Users retailer) {
        if (Objects.isNull(retailer)) {
            return;
        }
        cleanTxnDetails.setRetailerUserId(retailer.getId())
                .setRetailerUserPhone(retailer.getPhone())
                .setRetailerUserName(retailer.getUsername())
                .setRetailerUserLedgerCode(retailer.getLedgerCode())
                .setCityName(retailer.getCityCode())
                .setCityCode(retailer.getCityCode());
    }

    private void fillProductInfo(CleanTxnDetails cleanTxnDetails, Products product) {
        cleanTxnDetails.setProductName(product.getName())
                .setProductDescription(product.getDescription())
                .setProductChargeUnit(product.getChargeUnit());
    }

    private void fillProductItemInfo(CleanTxnDetails cleanTxnDetails, ProductItems productItem) {
        cleanTxnDetails.setProductItemPrice(productItem.getPrice())
                .setProductItemSpecDesc(productItem.getSpecDesc())
                .setProductId(productItem.getProductId());
    }

    private void fillOrderItemInfo(CleanTxnDetails cleanTxnDetails, OrderItems orderItem) {
        BigDecimal payFeeRatio = paymentsService.getPaymentFeeRatioByOrderId(orderItem.getOrderId());
        cleanTxnDetails.setOrderItemId(orderItem.getId())
                .setOrderItemQuantity(orderItem.getQuantity())
                .setOrderItemPrice(orderItem.getPrice())
                .setOrderItemPricePerUnit(orderItem.getPricePerUnit())
                .setRefundRate(orderItem.getRefundRate())
                .setProductItemId(orderItem.getProductItemId())
                .setProductItemSku(orderItem.getProductItemSku())
                .setTxnAmount(orderItem.getPrice().setScale(2, CommonConstant.ROUNDING_MODE))
                .setYeepayFee(orderItem.getPrice().multiply(payFeeRatio).setScale(2, CommonConstant.ROUNDING_MODE));
    }

    private void fillOrderInfo(CleanTxnDetails cleanTxnDetails, Orders orders) {
        cleanTxnDetails.setOrderId(orders.getId())
                .setOrderCode(orders.getCode());
    }


}